package com.wyj.classloader;

import java.io.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * TODO:  自定义的类加载器【子类优先】
 *        参考资料： https://segmentfault.com/a/1190000008669892
 *
 * @author wuyijie
 * @date 2021/12/16 1:44 下午
 */
public class MyWebAppLoader extends ClassLoader {

    // public static void main(String[] args) throws MalformedURLException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    //     MyWebAppLoader myWebAppLoader = new MyWebAppLoader("/servlet1/");
    //     Class<?> aClass = myWebAppLoader.findClass("com.demo.HelloServlet");
    //
    // }

    /**
     * lib:表示加载的文件在jar包中
     * 类似tomcat就是{PROJECT}/WEB-INF/lib/
     */
    private final String lib;
    /**
     * classes:表示加载的文件是单纯的class文件
     * 类似tomcat就是{PROJECT}/WEB-INF/classes/
     */
    private final String classes;
    /**
     * 采取将所有的jar包中的class读取到内存中
     * 然后如果需要读取的时候，再从map中查找
     */
    private final Map<String, byte[]> map;

    /**
     * 只需要指定项目路径就好
     * 默认jar加载路径是目录下{PROJECT}/WEB-INF/lib/
     * 默认class加载路径是目录下{PROJECT}/WEB-INF/classes/
     *
     * @param webAppName 项目名称（）
     */
    public MyWebAppLoader(String webAppName) {
        //设置系统类加载器AppClassLoader为当前加载器的父类
        super(webAppName, Thread.currentThread().getContextClassLoader());
        //得到真是的文件路径
        String webPath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("webApps")).getPath();
        webPath = webPath + "/" + webAppName + "/";
        lib = webPath + "WEB-INF/lib/";
        classes = webPath + "WEB-INF/classes/";
        map = new HashMap<String, byte[]>(64);
        //预加载jar文件
        preReadJarFile();
        System.out.println("【" + this + "】中加载的类数量为：" + map.size() );
    }

    /**
     * 按照父类的机制，如果在父类中没有找到的类
     * 才会调用这个findClass来加载
     * 这样只会加载放在自己目录下的文件
     * 而系统自带需要的class并不是由这个加载
     */
    @Override
    protected Class<?> findClass(String name) {
        try {
            byte[] result = getClassFromFileOrMap(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * TODO: 从指定的classes文件夹下找到文件
     *
     * @param name classname
     * @return byte[]
     * @author wuyijie
     * @date 2021/12/16 11:38 上午
     */
    private byte[] getClassFromFileOrMap(String name) {
        String classPath = classes + name.replace('.', File.separatorChar) + ".class";
        File file = new File(classPath);
        if (file.exists()) {
            InputStream input = null;
            try {
                input = new FileInputStream(file);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                while ((bytesNumRead = input.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        } else {
            if (map.containsKey(name)) {
                //去除map中的引用，避免GC无法回收无用的class文件
                return map.remove(name);
            }
        }
        return null;
    }

    /**
     * 预读lib下面的包
     */
    private void preReadJarFile() {
        List<File> list = scanDir();
        for (File f : list) {
            JarFile jar;
            try {
                jar = new JarFile(f);
                readJar(jar);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 读取一个jar包内的class文件，并存在当前加载器的map中
     *
     * @param jar JarFile
     */
    private void readJar(JarFile jar) throws IOException {
        Enumeration<JarEntry> en = jar.entries();
        while (en.hasMoreElements()) {
            JarEntry je = en.nextElement();
            String name = je.getName();
            if (name.endsWith(".class")) {
                String clss = name.replace(".class", "").replaceAll("/", ".");
                if (this.findLoadedClass(clss) != null) {
                    continue;
                }

                InputStream input = jar.getInputStream(je);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                while ((bytesNumRead = input.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                byte[] cc = baos.toByteArray();
                input.close();
                map.put(clss, cc);//暂时保存下来
            }
        }
    }

    /**
     * TODO: 扫描lib下面的所有jar包
     *
     * @return java.util.List<java.io.File>
     * @author wuyijie
     * @date 2021/12/16 11:36 上午
     */
    private List<File> scanDir() {
        List<File> list = new ArrayList<File>();
        File[] files = new File(lib).listFiles();
        if (null != files && files.length > 0) {
            for (File f : files) {
                if (f.isFile() && f.getName().endsWith(".jar")) {
                    list.add(f);
                }
            }
        }
        return list;
    }

    /**
     * TODO: 添加一个jar包到加载器中去。
     *
     * @param jarPath jarPath
     * @author wuyijie
     * @date 2021/12/16 11:37 上午
     */
    public void addJar(String jarPath) throws IOException {
        File file = new File(jarPath);
        if (file.exists()) {
            JarFile jar = new JarFile(file);
            readJar(jar);
        }
    }
}
